业务优化案例一则
背景
前一段时间著名的"开封菜"在有赞开通了小程序,为了推广其小程序,他们从8月6号开始每天10点做拼团活动,1万人或者2000人成团,用户购买之后可以进行分享给朋友进行裂变。 以上是活动的基础场景。
做活动之前还担心我们的MySQL能不能扛住大流量的压力,现在看来其实根本不用担心,因为"墨菲定律"告诉我们:如果你担心某种情况发生,那么它就更有可能发生。
现象
8.6-8.10的拼团活动还算顺利,但是因为前面几次的预热到周六消费高峰的时候,瞬时压力是平时的3倍,导致db thread_running飙高到250左右,基本不能对外提供服务。
从 innodb status 里面查看236个会话请求都在等待排队处理,23个正在被执行。
排查
相信很多人遇到类似的情况,因为业务或者数据库瞬时出现问题,人接收到报警,再等登陆到db服务器上其实已经滞后起码3-5min了,此时"案发现场"没有得到有效记录,导致排查瞬时的问题变得非常困得。基于此种问题,我们在ZanDB系统开发了数据库服务器性能快照功能,当探测到出现性能问题时,比如thread_running飙高超过24,就记录会话,io ,innodb数据到指定的文件,方便排查。
通过慢查询日志以及当时的processlist_3343.文件分析,我们找到一条可疑的SQL,出现问题的时刻,该sql占用大部分innodb处理通道。
select id,kdt_id,order_no,activity_id,xxxxx
where xxid =110
and group_id=119
and is_pay=1 order by id ASC limit 100;
该sql每次执行耗时10ms左右,执行计划显示使用"Using where; Using filesort"。 我们从开发同学那边了解到该sql承担了获取最新成交买家记录的功能,如图(10个微信头像):
大致的情况可以还原:10点开始抢购拼团时,大量流量进来,每次用户点击开团购买按钮都会触发一次查询DB的请求,周六的流量是前面几次的2-3倍,导致之前隐藏的问题出现了。
复现
为了验证我们的猜想,我们将表逻辑导出一份到测试库,使用mysqlslap进行压测。
mysqlslap --no-defaults -uroot --create-schema=ump -S /srv/my3308/run/mysql.sock --number-of-queries=1000000 --concurrency=28 --query="begin;select id,xxxid,xxxx,zz,aa,bbfrom yy where xxxid =110 and xxxid=119 and is_pay=1 order by id ASC limit 100;commit;"
压测结果
并发设置为288 模拟生产中出现问题的时候的情况。
mysqlslap --no-defaults -uroot --create-schema=ump -S /srv/my3308/run/mysql.sock --number-of-queries=1000000 --concurrency=288 --query="begin;select id,xxxid,xxxx,zz,aa,bbfrom yy where xxxid =110 and xxxid=119 and is_pay=1 order by id ASC limit 100;commit;" 压测结果
通过上面的压测可以看出:使用线上的业务sql 并发28,280进行压测,发现usr cpu飙高到43而且thread_running会话已经占满innodb_thread_concurrency, 并发280时已经出现严重的排队,导致其他正常的请求大量延迟,出现超时。
如果将limit 减少为10 呢?设置并发度为28压测
mysqlslap --no-defaults -uroot --create-schema=ump -S /srv/my3308/run/mysql.sock --number-of-queries=1000000 --concurrency=28 --query="begin;select id,xxxid,xxxx,zz,aa,bbfrom yy where xxxid =110 and xxxid=119 and is_pay=1;commit;"
设置并发度为288压测的压测结果
从上面的测试结果来看 减少获取数据量的个数以及去掉不必要的order by 排序,qps性能有50倍的提升。不过及时limit 10,高并发依然会带来数据库稳定性风险。还需要进一步优化。
解决
至此性能问题得以发现,如何解决呢?调整索引?减少排序?我们从源头上分析,该sql的功能是展示最新已经付款的人员,是否需要最新?如果是最开始成交的10个人是否ok?实际上参加活动的人关心的是自己是否参团成功,至于其他人谁参加了,对自己没有影响。具体的优化建议:
去掉order by id ,减少不必要的排序。
修改limit 100 为limit 10 ,因为前端页面展示只需要10个人即可。
缓存参团成功的人员信息,没有必要每个人点击参团页面都要查询db。
闲扯
性能优化是一件很有意思的事情。我们可以使用一张图来阐述数据库优化的核心思想:
(图片来自斗佛的blog)
这张漏斗优化法则可以使用“三减少一增加”来概括,减少磁盘访问,减少网络传输,减少CPU以及内存开销,增加硬件资源。 从图中我们可以看到优化业务效果最显著的是 减少对数据(存储)的访问。此次案例也是一样,减少不必要的数据拉取和额外的排序,性能提升显著。